/*
 * utility functions
 *
 * Copyright (C) 2014-2018 LastPass.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 *
 * See LICENSE.OpenSSL for more details regarding this exception.
 *
 * reallocarray is:
 *     Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>.
 * strlcpy and strlcat are:
 *     Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>.
 * For reallocarray, strlcpy, and strlcat:
 *     Permission to use, copy, modify, and distribute this software for any
 *     purpose with or without fee is hereby granted, provided that the above
 *     copyright notice and this permission notice appear in all copies.
 *     THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *     WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *     MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *     ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *     WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *     OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "util.h"
#include "process.h"
#include "terminal.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <openssl/rand.h>

void warn(const char *err, ...)
{
	char message[4096];
	va_list params;

	va_start(params, err);
	vsnprintf(message, sizeof(message), err, params);
	va_end(params);

	terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD "Warning" TERMINAL_RESET ": %s\n", message);
}

void warn_errno(const char *err, ...)
{
	char message[4096], *error_message;
	va_list params;

	error_message = strerror(errno);

	va_start(params, err);
	vsnprintf(message, sizeof(message), err, params);
	va_end(params);

	terminal_fprintf(stderr, TERMINAL_FG_YELLOW TERMINAL_BOLD "WARNING" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET ": %s\n", error_message, message);
}

_noreturn_ void die(const char *err, ...)
{
	char message[4096];
	va_list params;

	va_start(params, err);
	vsnprintf(message, sizeof(message), err, params);
	va_end(params);

	terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": %s\n", message);
	exit(1);
}
_noreturn_ void die_errno(const char *err, ...)
{
	char message[4096], *error_message;
	va_list params;

	error_message = strerror(errno);

	va_start(params, err);
	vsnprintf(message, sizeof(message), err, params);
	va_end(params);

	terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": " TERMINAL_FG_RED "%s" TERMINAL_RESET ": %s\n", error_message, message);
	exit(1);
}

void die_usage(const char *usage)
{
	terminal_fprintf(stderr, "Usage: %s %s\n", ARGV[0], usage);
	exit(1);
}

char ask_options(char *options, char def, const char *prompt, ...)
{
	va_list params;
	_cleanup_free_ char *response = NULL;
	size_t len = 0;
	unsigned int i;

	for (;;) {
		va_start(params, prompt);
		terminal_fprintf(stderr, TERMINAL_FG_YELLOW);
		vfprintf(stderr, prompt, params);
		terminal_fprintf(stderr, TERMINAL_RESET);
		va_end(params);
		terminal_fprintf(stderr, " [");
	        for (i = 0; i < strlen(options); i++) {
			if (options[i] == def) {
				terminal_fprintf(stderr,TERMINAL_BOLD "%c" TERMINAL_RESET, toupper(options[i]));
			} else {
				terminal_fprintf(stderr, "%c", options[i]);
			}
			if (i < strlen(options)-1)
				terminal_fprintf(stderr, "/");
			else
				terminal_fprintf(stderr, "] ");
		}
		if (getline(&response, &len, stdin) < 0)
			die("aborted response.");
		strlower(response);

		if (!strcmp("\n", response))
			return def;

		if (strlen(response) && strchr(options, response[0]))
			return response[0];

		terminal_fprintf(stderr, TERMINAL_FG_RED TERMINAL_BOLD "Error" TERMINAL_RESET ": Response not understood.\n");
		free(response);
		response = NULL;
		len = 0;
	}
}

bool ask_yes_no(bool default_yes, const char *prompt, ...)
{
	va_list params;
	char message[4096];

	va_start(params, prompt);
	vsnprintf(message, sizeof(message), prompt, params);
	va_end(params);

	return ask_options("yn", (default_yes) ? 'y' : 'n', message) == 'y';
}

void *xmalloc(size_t size)
{
	void *ret = malloc(size);
	if (likely(ret))
		return ret;
	die_errno("malloc(%zu)", size);
}
void *xcalloc(size_t nmemb, size_t size)
{
	void *ret = calloc(nmemb, size);
	if (likely(ret))
		return ret;
	die_errno("calloc(%zu, %zu)", nmemb, size);
}
void *xrealloc(void *ptr, size_t size)
{
	void *ret = realloc(ptr, size);
	if (likely(ret))
		return ret;
	die_errno("realloc(%p, %zu)", ptr, size);
}
void *reallocarray(void *optr, size_t nmemb, size_t size)
{
	if (!size || !nmemb) {
		errno = ENOMEM;
		return NULL;
	}
	if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
	    nmemb > 0 && SIZE_MAX / nmemb < size) {
		errno = ENOMEM;
		return NULL;
	}
	return realloc(optr, size * nmemb);
}
void *xreallocarray(void *ptr, size_t nmemb, size_t size)
{
	void *ret = reallocarray(ptr, nmemb, size);
	if (likely(ret))
		return ret;
	die_errno("reallocarray(%p, %zu, %zu)", ptr, nmemb, size);
}

void *xstrdup(const char *str)
{
	void *ret = strdup(str);
	if (likely(ret))
		return ret;
	die_errno("strdup(%p)", (void *) str);
}
void *xstrndup(const char *str, size_t maxlen)
{
	void *ret = strndup(str, maxlen);
	if (likely(ret))
		return ret;
	die_errno("strndup(%p, %zu)", (void *) str, maxlen);
}
int xasprintf(char **strp, const char *fmt, ...)
{
	va_list params;
	int ret;

	va_start(params, fmt);
	ret = xvasprintf(strp, fmt, params);
	va_end(params);

	return ret;
}
int xvasprintf(char **strp, const char *fmt, va_list ap)
{
	int ret;

	ret = vasprintf(strp, fmt, ap);
	if (ret == -1)
		die_errno("asprintf(%p, %s, ...)", (void *)strp, fmt);

	return ret;
}

#ifdef __GLIBC__
size_t strlcpy(char *dst, const char *src, size_t siz)
{
	char *d = dst;
	const char *s = src;
	size_t n = siz;

	/* Copy as many bytes as will fit */
	if (n != 0) {
		while (--n != 0) {
			if ((*d++ = *s++) == '\0')
				break;
		}
	}

	/* Not enough room in dst, add NUL and traverse rest of src */
	if (n == 0) {
		if (siz != 0)
			*d = '\0';		/* NUL-terminate dst */
		while (*s++)
			;
	}

	return (s - src - 1);	/* count does not include NUL */
}
size_t strlcat(char *dst, const char *src, size_t siz)
{
	char *d = dst;
	const char *s = src;
	size_t n = siz;
	size_t dlen;

	/* Find the end of dst and adjust bytes left but don't go past end */
	while (n-- != 0 && *d != '\0')
		d++;
	dlen = d - dst;
	n = siz - dlen;

	if (n == 0)
		return (dlen + strlen(s));
	while (*s != '\0') {
		if (n != 1) {
			*d++ = *s;
			n--;
		}
		s++;
	}
	*d = '\0';

	return (dlen + (s - src));	/* count does not include NUL */
}
#else
char *strchrnul(const char *s, int c)
{
	char *p = strchr(s, c);
	if (!p)
		p = (char *)s + strlen(s);
	return p;
}
#endif

void strlower(char *str)
{
	for (; *str; ++str)
		*str = tolower(*str);
}
void strupper(char *str)
{
	for (; *str; ++str)
		*str = toupper(*str);
}
char *xstrlower(const char *str)
{
	char *copy = xstrdup(str);
	strlower(copy);
	return copy;
}
char *xstrupper(const char *str)
{
	char *copy = xstrdup(str);
	strupper(copy);
	return copy;
}
char *xultostr(unsigned long num)
{
	char *str;
	xasprintf(&str, "%ld", num);
	return str;
}

bool starts_with(const char *str, const char *start)
{
	for (; ; ++str, ++start) {
		if (!*start)
			return true;
		else if (*str != *start)
			return false;
	}
}
bool ends_with(const char *str, const char *end)
{
	int str_len = strlen(str);
	int end_len = strlen(end);
	if (str_len < end_len)
		return false;
	else
		return !strcmp(str + str_len - end_len, end);
}
char *trim(char *str)
{
	int start, i;

	for (start = 0; isspace(str[start]) && str[start]; ++start);

	for (i = 0; str[i + start]; ++i)
		str[i] = str[i + start];
	str[i] = '\0';

	for (--i; i >= 0 && isspace(str[i]); --i)
		str[i] = '\0';

	return str;
}

void xstrappend(char **str, const char *suffix)
{
	if (!*str) {
		*str = xstrdup(suffix);
		return;
	}
	size_t len = strlen(*str) + strlen(suffix) + 1;
	*str = xrealloc(*str, len);
	strlcat(*str, suffix, len);
}
void xstrappendf(char **str, const char *suffixfmt, ...)
{
	_cleanup_free_ char *fmt = NULL;
	va_list args;

	va_start(args, suffixfmt);
	xvasprintf(&fmt, suffixfmt, args);
	va_end(args);

	xstrappend(str, fmt);
}
void xstrprepend(char **str, const char *prefix)
{
	if (!*str) {
		*str = xstrdup(prefix);
		return;
	}
	size_t len = strlen(*str) + strlen(prefix) + 1;
	char *new = xmalloc(len);
	strlcpy(new, prefix, len);
	strlcat(new, *str, len);
	free(*str);
	*str = new;
}
void xstrprependf(char **str, const char *suffixfmt, ...)
{
	_cleanup_free_ char *fmt = NULL;
	va_list args;

	va_start(args, suffixfmt);
	xvasprintf(&fmt, suffixfmt, args);
	va_end(args);

	xstrprepend(str, fmt);
}

void secure_clear(void *ptr, size_t len)
{
	if (!ptr)
		return;

	memset(ptr, 0, len);
	/* prevent GCC / LLVM from optimizing out memset */
	asm volatile("" : : "r"(ptr) : "memory");
}

void secure_clear_str(char *str)
{
	if (!str)
		return;

	secure_clear(str, strlen(str));
}

void *secure_resize(void *ptr, size_t oldlen, size_t newlen)
{
	/* open-coded realloc, with a secure memset in the middle */
	void *newptr = xmalloc(newlen);
	if (ptr) {
		memcpy(newptr, ptr, min(oldlen, newlen));
		secure_clear(ptr, oldlen);
		free(ptr);
	}
	return newptr;
}

static char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
_unroll_ void bytes_to_hex(const unsigned char *bytes, char **hex, size_t len)
{
	if (!*hex)
		*hex = xmalloc(len * 2 + 1);
	for (size_t i = 0; i < len; ++i) {
		(*hex)[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF];
		(*hex)[i * 2 + 1] = hex_digits[bytes[i] & 0xF];
	}
	(*hex)[len * 2] = '\0';
}

int hex_to_bytes(const char *hex, unsigned char **bytes)
{
	size_t len = strlen(hex);
	if (len % 2 != 0) {
		if (!*bytes)
			*bytes = xcalloc(1, 1);
		**bytes = '\0';
		return -EINVAL;
	}
	if (!*bytes)
		*bytes = xmalloc(len / 2 + 1);
	for (size_t i = 0; i < len / 2; ++i) {
		if (sscanf(&hex[i * 2], "%2hhx", (unsigned char *)(*bytes + i)) != 1) {
			fprintf(stderr, "%s\n", hex);
			**bytes = '\0';
			return -EINVAL;
		}
	}
	(*bytes)[len / 2] = '\0';
	return 0;
}

/* [min, max) */
unsigned long range_rand(unsigned long min, unsigned long max)
{
	unsigned long base_random, range, remainder, bucket;

	if (!RAND_bytes((unsigned char *)&base_random, sizeof(base_random)))
		die("Could not generate random bytes.");
	if (ULONG_MAX == base_random)
		return range_rand(min, max);
	range = max - min;
	remainder = ULONG_MAX % range;
	bucket = ULONG_MAX / range;
	if (base_random < ULONG_MAX - remainder)
		return min + base_random / bucket;
	return range_rand(min, max);
}

void get_random_bytes(unsigned char *buf, size_t len)
{
	if (!RAND_bytes(buf, len))
		die("Could not generate random bytes.");
}

const char *bool_str(bool val)
{
	return val ? "1" : "0";
}
